#include "Render/PiccoloRenderer.h"

#include "Graphics/PiccoloVKQueue.h"

#include "PiccoloApplication.h"

namespace Piccolo
{
    PiccoloRenderer::PiccoloRenderer()
    {
        PiccoloRenderContext* renderContext = PiccoloApplication::GetAppContext()->renderContext;
        PiccoloVKDevice* device = renderContext->GetDevice();
        mImageAvailableSemaphores.resize(RENDERER_NUM_BUFFER);
        mSubmittedSemaphores.resize(RENDERER_NUM_BUFFER);
        mFrameFences.resize(RENDERER_NUM_BUFFER);

        VkSemaphoreCreateInfo semaphoreInfo =
            {
            .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
            .pNext = nullptr,
            .flags = 0
        };
        VkFenceCreateInfo fenceInfo =
        {
            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
            .pNext = nullptr,
            .flags = VK_FENCE_CREATE_SIGNALED_BIT
        };
        for (int i = 0; i < RENDERER_NUM_BUFFER; ++i)
        {
            CALL_VK(vkCreateSemaphore(device->GetLogicDevice(), &semaphoreInfo, nullptr, &mImageAvailableSemaphores[i]));
            CALL_VK(vkCreateSemaphore(device->GetLogicDevice(), &semaphoreInfo, nullptr, &mSubmittedSemaphores[i]));
            CALL_VK(vkCreateFence(device->GetLogicDevice(), &fenceInfo, nullptr, &mFrameFences[i]));
        }
    }

    PiccoloRenderer::~PiccoloRenderer()
    {
        PiccoloRenderContext* renderContext = PiccoloApplication::GetAppContext()->renderContext;
        PiccoloVKDevice* device = renderContext->GetDevice();
        for (const auto& item : mImageAvailableSemaphores)
        {
            VK_D(Semaphore, device->GetLogicDevice(), item);
        }
        for (const auto& item : mSubmittedSemaphores)
        {
            VK_D(Semaphore, device->GetLogicDevice(), item);
        }
        for (const auto& item : mFrameFences)
        {
            VK_D(Fence, device->GetLogicDevice(), item);
        }
    }

    bool PiccoloRenderer::Begin(int32_t *outImageIndex)
    {
        PiccoloRenderContext* renderContext = PiccoloApplication::GetAppContext()->renderContext;
        PiccoloVKDevice* device = renderContext->GetDevice();
        PiccoloVKSwapchain* swapchain = renderContext->GetSwapchain();

        bool bShouldUpdateTarget = false;

        CALL_VK(vkWaitForFences(device->GetLogicDevice(), 1, &mFrameFences[mCurrentBuffer], VK_TRUE, UINT64_MAX));
        CALL_VK(vkResetFences(device->GetLogicDevice(), 1, &mFrameFences[mCurrentBuffer]));

        // 从交换链获取image
        VkResult ret = swapchain->AcquireImage(outImageIndex, mImageAvailableSemaphores[mCurrentBuffer]);
        if (ret == VK_ERROR_OUT_OF_DATE_KHR)
        {
            CALL_VK(vkDeviceWaitIdle(device->GetLogicDevice()));
            VkExtent2D originExtent = { swapchain->GetWidth(), swapchain->GetHeight() };
            bool bSuccess = swapchain->RecreateSwapchain();

            VkExtent2D newExtent = { swapchain->GetWidth(), swapchain->GetHeight() };
            if (bSuccess && (originExtent.width != newExtent.width || originExtent.height != newExtent.height))
            {
                bShouldUpdateTarget = true;
            }
            ret = swapchain->AcquireImage(outImageIndex, mImageAvailableSemaphores[mCurrentBuffer]);
            if (ret != VK_SUCCESS && ret != VK_SUBOPTIMAL_KHR)
            {
                LOG_E("Recreate swapchain error: {0}", vk_result_string(ret));
            }
        }
        return bShouldUpdateTarget;
    }

    bool PiccoloRenderer::End(int32_t imageIndex, const std::vector<VkCommandBuffer> &cmdBuffers)
    {
        PiccoloRenderContext* renderContext = PiccoloApplication::GetAppContext()->renderContext;
        PiccoloVKDevice* device = renderContext->GetDevice();
        PiccoloVKSwapchain* swapchain = renderContext->GetSwapchain();

        bool bShouldUpdateTarget = false;

        // 提交 command buffer 到队列
        device->GetFirstGraphicQueue()->Submit({ cmdBuffers }, { mImageAvailableSemaphores[mCurrentBuffer] }, { mSubmittedSemaphores[mCurrentBuffer] }, mFrameFences[mCurrentBuffer]);
        // 呈现 present
        VkResult ret = swapchain->Present(imageIndex, { mSubmittedSemaphores[mCurrentBuffer] });
        if (ret == VK_SUBOPTIMAL_KHR)
        {
            CALL_VK(vkDeviceWaitIdle(device->GetLogicDevice()));
            VkExtent2D originExtent = { swapchain->GetWidth(), swapchain->GetHeight() };
            bool bSuccess = swapchain->RecreateSwapchain();

            VkExtent2D newExtent = { swapchain->GetWidth(), swapchain->GetHeight() };
            if (bSuccess && (originExtent.width != newExtent.width || originExtent.height != newExtent.height))
            {
                bShouldUpdateTarget = true;
            }
        }
        CALL_VK(vkDeviceWaitIdle(device->GetLogicDevice()));
        mCurrentBuffer = (mCurrentBuffer + 1) % RENDERER_NUM_BUFFER;
        return bShouldUpdateTarget;
    }
}
